package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"strings"

	"gitee.com/lipore/plume/db_gorm/gen/code"
	"gitee.com/lipore/plume/db_gorm/gen/generator"
	"gitee.com/lipore/plume/db_gorm/gen/gorm/mysql"
	"gitee.com/lipore/plume/db_gorm/gen/spec"
)

func main() {
	pwd, err := os.Getwd()
	if err != nil {
		log.Println(err)
	}
	fmt.Println(pwd) // for example /home/user

	flag.Usage = printUsage

	versionPtr := flag.Bool("version", false, "print version of repogen")
	domainPtr := flag.String("domain", "", "domain package path")
	persistPtr := flag.String("persist", "", "persistence package path")
	modelPtr := flag.String("model", "", "model struct name")
	repoPtr := flag.String("repo", "", "repository interface name")

	flag.Parse()

	if *versionPtr {

		printVersion()
		return
	}

	if *domainPtr == "" || *persistPtr == "" || *modelPtr == "" || *repoPtr == "" {
		printUsage()
		return
	}

	persistPath := path.Join(pwd, *persistPtr)
	domainPath := path.Join(pwd, *domainPtr)

	genFile := path.Join(persistPath, strings.ToLower(fmt.Sprintf("%s_repository.go", *modelPtr)))
	_ = os.Remove(genFile)

	code, err := generateFromRequest(domainPath, persistPath, *modelPtr, *repoPtr)
	if err != nil {
		panic(err)
	}

	f, _ := os.Create(genFile)
	defer f.Close()

	if _, err := f.WriteString(code); err != nil {
		panic(err)
	}
}

func printVersion() {
	fmt.Println("0.1.0")
}

const usageText = `generates repository implemention from repository  interface

Supported options:`

func printUsage() {
	fmt.Println(usageText)
	flag.PrintDefaults()
}

func generateFromRequest(domainPath, persistencePath, modelName, repoName string) (string, error) {
	domainSpec, err := code.ExtractComponentsByPath(domainPath)
	if err != nil {
		return "", err
	}
	domainSpec, err = code.ExportFile(domainSpec)
	if err != nil {
		return "", err
	}

	persistenceSpec, err := code.ExtractComponentsByPath(persistencePath)
	if err != nil {
		return "", err
	}

	domainStruct, ok := domainSpec.Structs.ByName(modelName)
	if !ok {
		return "", errors.New("domain struct not found")
	}
	persistStruct, ok := persistenceSpec.Structs.ByName(fmt.Sprintf("%sEntity", modelName))
	if !ok {
		return "", errors.New("persistence struct not found")
	}
	intf, ok := domainSpec.Interfaces.ByName(repoName)
	if !ok {
		return "", errors.New("repository interface not found")
	}
	var methodSpecs []spec.MethodSpec
	for _, method := range intf.Methods {
		exist := false
		for _, m := range persistenceSpec.Methods {
			if m.Name == method.Name && (m.Receiver.Code() == repoName || m.Receiver.Code() == fmt.Sprintf("*%s", repoName)) {
				exist = true
			}
		}
		if exist {
			continue
		}
		methodSpec, err := spec.ParseInterfaceMethod(persistenceSpec.Structs, domainStruct, method)
		if err != nil {
			return "", err
		}
		methodSpecs = append(methodSpecs, methodSpec)
	}

	genMapper := true
	entityName := fmt.Sprintf("%sEntity", modelName)
	for _, m := range persistenceSpec.Methods {
		if m.Receiver == nil {
			continue
		}
		if (m.Receiver.Code() == entityName || m.Receiver.Code() == fmt.Sprintf("*%s", entityName)) &&
			(m.Name == "toDomainObject" || m.Name == "fromDomainObject") {
			genMapper = false
		}
	}

	genConstructor := true
	if _, ok := persistenceSpec.Structs.ByName(repoName); ok {
		genConstructor = false
	}

	return generator.GenerateRepository(persistenceSpec.PackageName, domainSpec.PackageName, persistStruct, domainStruct,
		repoName, methodSpecs, mysql.NewRepositoryGenerator(persistStruct, code.ExternalType{Name: repoName, PackageAlias: domainSpec.PackageName}), genMapper, genConstructor)
}
